﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using FogBugzPd.Core;
using FogBugzPd.Core.FogBugzApi;
using FogBugzPd.Infrastructure;
using FogBugzPd.Web.Models.Project;
using FogBugzPd.Web.Utils;
using FogLampz.Model;

namespace FogBugzPd.Web.Controllers
{
	public class ProjectController : ControllerBase
	{
		//coding convention from http://stackoverflow.com/questions/242534/c-sharp-naming-convention-for-constants

		private static readonly Regex CodeFreezeSubstringRegex = new Regex(
            @"\(CF *(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2}(?:\d{2})?)\)",
            RegexOptions.Singleline | RegexOptions.Compiled);

		private const int DefaultNumOfThreads = 20;

		[CustomAuthorize]
		public ActionResult Index()
		{
			return RedirectToAction("List");
		}

		[CustomAuthorize]
		public ActionResult List()
		{
			ViewBag.MasterTitle = "Projects and Milestones";
			return View("List");
		}


		[CustomAuthorize]
		public ActionResult AsyncList(int timeOffest)
		{
			UserContext.TimeZoneOffset = timeOffest;
			var model = CreateListViewModel();

			model.ShowSubProjects = FbAccountContext.Current.Settings.AllowSubProjects;

			return PartialView("_AsyncList", model);
		}

		[CustomAuthorize]
		public ActionResult Dashboard(int projectId, int milestoneId, int? subProjectParentCaseId = null)
		{
			var viewModel = new DashboardViewModel();
			viewModel.Setup(projectId, milestoneId, subProjectParentCaseId, true);
			
			UpdateViewBagForProjectLayout(viewModel);
			
			return View(viewModel);
		}



		[CustomAuthorize]
		public ActionResult AsyncDashboard(int projectId, int milestoneId, int? subProjectParentCaseId = null)
		{
			var today = DateTime.Today;

			var model = new AsyncDashboardViewModel();
			model.Setup(projectId, milestoneId, subProjectParentCaseId);

			string cacheKey = MsCacheKey.GenCaseSetKey(projectId, milestoneId, subProjectParentCaseId);
			model.MsCache.CreatedAt = MsCache.GetObjectExpirationTime(cacheKey);

			UpdateViewBagForProjectLayout(model);

			IList<Case> cases = new List<Case>();
	
			foreach (var casesWithMilestone in model.CaseSet.Cases.Where(casesWithMilestone => casesWithMilestone.IndexFixFor == milestoneId))
			{
				cases.Add(casesWithMilestone);
			}
			
			if (model.CaseSet.Milestone != null)
			{
				model.DatesSection.StartDate = model.CaseSet.Milestone.DateStart;
				if (model.DatesSection.StartDate.HasValue)
				{
					model.DatesSection.StartDateDaysRemaining = DateUtils.Difference(today, model.DatesSection.StartDate.Value);
				}


                model.DatesSection.CodeFreeze = GetCodeFreezeFromName(model.CaseSet.Milestone.Name);
                if (model.DatesSection.CodeFreeze.HasValue)
                {
                    model.DatesSection.CodeFreezeDaysRemaining = DateUtils.Difference(today, model.DatesSection.CodeFreeze.Value);
                    model.DatesSection.CodeFreezeHolidaysBefore = HolidaysUtils.GetHolidaysInRange(today,
                                                                                                   model.DatesSection.CodeFreeze.Value);
                }

				model.DatesSection.Rollout = model.CaseSet.Milestone.DateRelease;
				if (model.DatesSection.Rollout.HasValue)
				{
					model.DatesSection.RolloutDaysRemaining = DateUtils.Difference(today, model.DatesSection.Rollout.Value);
					model.DatesSection.RolloutHolidaysBefore = HolidaysUtils.GetHolidaysInRange(today, model.DatesSection.Rollout.Value);
				}

				CalculatePlotTimes(model.DatesSection);

				model.DatesSection.IsActiveProject = today >= model.DatesSection.StartDate && today <= model.DatesSection.Rollout;
			}

			model.CasesSection.Total = cases.Count();
			model.CasesSection.Active = cases.Count(CaseUtils.IsActive);
			model.CasesSection.Resolved = cases.Count(CaseUtils.IsResolved);
			model.CasesSection.Verified = cases.Count(CaseUtils.IsResolvedVerified);
			model.CasesSection.Closed = cases.Count(CaseUtils.IsClosed);

			model.TimeSection.TotalEstimated = cases.Sum(@case => @case.HoursCurrentEstimate.GetValueOrDefault(0));
			model.TimeSection.Elapsed = cases.Sum(@case => @case.HoursElapsed.GetValueOrDefault(0));
			model.TimeSection.ActiveEstimated = cases.Where(CaseUtils.IsActive).Sum(@case => @case.HoursCurrentEstimate.GetValueOrDefault(0));
			model.TimeSection.ActiveRemaining = cases.Where(CaseUtils.IsActive).Sum(@case => Math.Max(
				0, @case.HoursCurrentEstimate.GetValueOrDefault(0) - @case.HoursElapsed.GetValueOrDefault(0)));

			model.EstimatesSection.WithEstimates = cases.Where(CaseUtils.IsActive).Count(@case => @case.HoursCurrentEstimate.GetValueOrDefault(0) > 0);
			model.EstimatesSection.WithoutEstimates = cases.Count(CaseUtils.IsActive) - model.EstimatesSection.WithEstimates;

			//at this point model.EstimatesSection.WithoutEstimates can be negative.
			//it is possible when cases which has subcases (IndexBugChildren.Count>0) are estimated too
			if (model.EstimatesSection.WithoutEstimates < 0)
				model.EstimatesSection.WithoutEstimates = 0;
			model.EstimatesSection.GoingOverEstimate = cases.Where(CaseUtils.IsActive).Count(
				@case => @case.HoursElapsed.GetValueOrDefault(0) > @case.HoursCurrentEstimate.GetValueOrDefault(0));

			model.AccuracySection.EstimatedTime = cases.Where(@case => CaseUtils.IsResolved(@case) || CaseUtils.IsClosed(@case)).Sum(@case => @case.HoursCurrentEstimate.GetValueOrDefault(0));
			model.AccuracySection.ActualTime = cases.Where(@case => CaseUtils.IsResolved(@case) || CaseUtils.IsClosed(@case)).Sum(@case => @case.HoursElapsed.GetValueOrDefault(0));

			return PartialView("_AsyncDashboard", model);
		}

		private static void CalculatePlotTimes(AsyncDashboardViewModel.DatesSectionViewModel datesTab)
		{
			var plotTimes = new List<AsyncDashboardViewModel.DatesSectionViewModel.PlotTime>();

			var dtCurrent = DateTime.Now;

			// 0. Start time
			if (datesTab.StartDate.HasValue) plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Start Date", datesTab.StartDate.Value));

			// 1. Current time
			plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Now", dtCurrent));

			if (datesTab.StartDate.HasValue)
			{
				datesTab.MinimumTime = TimeUtils.Min(dtCurrent, datesTab.StartDate.Value);
			}

            // 2. Code freeze time
            var codeFreeze = datesTab.CodeFreeze;
            if (codeFreeze.HasValue) plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Code Freeze", codeFreeze.Value));

            datesTab.MinimumTime = (datesTab.StartDate.HasValue
                                        ? TimeUtils.Min(dtCurrent, datesTab.StartDate.Value)
                                        : (codeFreeze.HasValue
                                               ? TimeUtils.Min(dtCurrent, codeFreeze.Value)
                                               : dtCurrent)).AddDays(-7);
            
            // 3. Release date
			if (datesTab.Rollout != null)
			{
				plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Release", datesTab.Rollout.Value));

				datesTab.MaximumTime = TimeUtils.Max(dtCurrent, datesTab.Rollout.Value).AddDays(7);

				datesTab.PlotTimes = plotTimes.OrderBy(plotTime => plotTime.DateTime).ToArray();
			}


		}

        private static DateTime? GetCodeFreezeFromName(string name)
        {
            Match match = CodeFreezeSubstringRegex.Match(name);

            int day, month, year;

            if (match.Success &&
                int.TryParse(match.Groups["day"].Value, out day) &&
                int.TryParse(match.Groups["month"].Value, out month) &&
                int.TryParse(match.Groups["year"].Value, out year))
            {
                if (year < 100) year += 2000;

                return new DateTime(year, month, day);
            }

            return null;
        }

		[CustomAuthorize]
		public ActionResult Priority(int projectId, int milestoneId, int? subProjectParentCaseId)
		{
			var model = new PriorityViewModel();

			model.Setup(projectId, milestoneId, subProjectParentCaseId);

			string cacheKey = MsCacheKey.GenCaseSetKey(projectId, milestoneId, subProjectParentCaseId);
			model.MsCache.CreatedAt = MsCache.GetObjectExpirationTime(cacheKey);

			UpdateViewBagForProjectLayout(model);

			return View(model);
		}

		[CustomAuthorize]
		public ActionResult Developer(int projectId, int milestoneId, int? subProjectParentCaseId)
		{
			var model = new DeveloperViewModel();

			model.Setup(projectId, milestoneId, subProjectParentCaseId);
			UpdateViewBagForProjectLayout(model);

			string cacheKey = MsCacheKey.GenCaseSetKey(projectId, milestoneId, subProjectParentCaseId);
			model.MsCache.CreatedAt = MsCache.GetObjectExpirationTime(cacheKey);

			return View(model);
		}

		[CustomAuthorize]
		public ActionResult QA(int projectId, int milestoneId, int? subProjectParentCaseId)
		{
			var model = new QAViewModel();

			model.Setup(projectId, milestoneId, subProjectParentCaseId);
			UpdateViewBagForProjectLayout(model);
			var persons = FogBugzGateway.GetPersons();

			string cacheKey = MsCacheKey.GenCaseSetKey(projectId, milestoneId, subProjectParentCaseId);
			model.MsCache.CreatedAt = MsCache.GetObjectExpirationTime(cacheKey);

			IList<Case> cases = new List<Case>();
			foreach (var casesWithMilestone in model.CaseSet.Cases.Where(casesWithMilestone => casesWithMilestone.IndexFixFor == milestoneId))
			{
				cases.Add(casesWithMilestone);
			}
		
			var qaListItems =
				persons.GroupJoin(cases, person => person.Index, @case => @case.IndexPersonAssignedTo,
								  (person, personCases) =>
								  {
									  var casesArr = personCases.ToArray();
									  return new QAListItem
										  {
											  TesterName = person.Name,
											  Estimate = casesArr.Where(CaseUtils.IsResolved).Sum(@case => CalcEstimateForCase(@case)),
											  CaseToVerify = casesArr.Count(CaseUtils.IsResolved),
											  DevelopmentTime = casesArr.Where(CaseUtils.IsResolved).Sum(@case => @case.HoursElapsed.GetValueOrDefault(0)),
								              VerifiedCases = casesArr.Count(CaseUtils.IsResolvedVerified)
										  };
								  })
					   .Where(li => li.CaseToVerify > 0 || li.VerifiedCases > 0);


			model.SetItems(qaListItems);

			model.CasesTotal = cases.Count();

			model.CasesReadyToBeTested = cases.Count(CaseUtils.IsResolved);
			model.CasesActive = cases.Count(CaseUtils.IsActive);
			model.CasesVerified = model.CaseSet.Cases.Count(CaseUtils.IsResolvedVerified);
			model.CasesClosed = cases.Count(CaseUtils.IsClosed);


			decimal activeCasesTestingTime =
				cases.Where(CaseUtils.IsActive).Sum(@case => CalcEstimateForCase(@case, true));
			decimal resolvedCasesTestingTime =
				cases.Where(CaseUtils.IsResolved).Sum(@case => CalcEstimateForCase(@case));
			decimal resolvedVerifiedCasesTestingTime =
				cases.Where(CaseUtils.IsResolvedVerified).Sum(@case => CalcEstimateForCase(@case));
			decimal closedCasesTestingTime =
				cases.Where(CaseUtils.IsClosed).Sum(@case => CalcEstimateForCase(@case));

			model.TotalTestingTime = (activeCasesTestingTime + resolvedCasesTestingTime + closedCasesTestingTime + resolvedVerifiedCasesTestingTime);
			model.RemainingTestingTime = (activeCasesTestingTime + resolvedCasesTestingTime);
			model.ReadyToBeTestedTime = resolvedCasesTestingTime;

			if (FbAccountContext.Current.Settings.AllowTestRail)
				model.TestRailPlansSummary = FogBugzToTestRailUtils.GetTestRailPlansSummary(milestoneId, projectId);

			return View(model);
		}

		private decimal CalcEstimateForCase(Case cs, bool currentEstimate = false)
		{
			decimal result = 0;
			
			var ratio = (decimal)FbAccountContext.Current.Settings.QaPercentage / 100;
			
			if (currentEstimate)
			{
				if (FbAccountContext.Current.Settings.AllowQaEstimates)
					result = ((decimal)cs.TestEstimate.GetValueOrDefault(0)) / 60m; // "Test Estimate" field value is in minutee
				else
					result = cs.HoursCurrentEstimate.GetValueOrDefault(0) * ratio;
			}
			else if (FbAccountContext.Current.Settings.AllowQaEstimates)
			{
				result = ((decimal)cs.TestEstimate.GetValueOrDefault(0))/60m; // "Test Estimate" field value is in minutes
			}
			else 
			{
				result = cs.HoursElapsed.GetValueOrDefault(0) * ratio;
			}

			return result;
		}


		private ProjectMilestoneList CreateListViewModel()
		{
			IList<Project> projects = FogBugzGateway.GetProjects();
			IList<FixFor> milestones = FogBugzGateway.GetMilestones();
			IList<Case> subProjectParentCases = FogBugzGateway.GetSubProjectParentCases();

			var projectMilestoneList = new ProjectMilestoneList(UserContext.FogBugzUrl);

			projectMilestoneList.GetList(projects, milestones, subProjectParentCases, DefaultNumOfThreads);

			return projectMilestoneList;
		}

		
		private void UpdateViewBagForProjectLayout(CaseSetViewModelBase viewModel)
		{
			ViewBag.ProjectName = viewModel.ProjectName;
			ViewBag.MilestoneName = viewModel.MilestoneName;

			if (viewModel.SubProjectParentCaseId.HasValue)
			{
				var subProjectParentCase = FogBugzGateway.GetSubProjectParentCase(viewModel.SubProjectParentCaseId.Value);
				ViewBag.SubProjectName = subProjectParentCase.Title;
			}

			if (viewModel.MilestoneId.HasValue)
			{
                var codeFreezeDate = GetCodeFreezeFromName(viewModel.MilestoneName);
                var endDate = viewModel.Milestone.DateRelease;
				var startDate = viewModel.Milestone.DateStart;
				int unixTime = 0;
				if (startDate.HasValue && DateTime.Now >= startDate)
				{
                    if (codeFreezeDate.HasValue && codeFreezeDate.Value > DateTime.Now)
                    {
                        unixTime = (int)(codeFreezeDate.Value - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
                        ViewBag.CountdownLabel = "Code Freeze in:";
                    }
                    else if (endDate.HasValue)
					{
						unixTime = (int)(endDate.Value - new DateTime(1970, 1, 1)).TotalSeconds;
						ViewBag.CountdownLabel = "Rollout in:";
					}
					if (unixTime > 0)
						ViewBag.CountdownUnixTime = unixTime;
				}
			}

		}
	}
}
